Pong

In this document you will implement a clone of the classic game of Pong by Atari. You will learn the basics of rendering entities and moving them through physical properties like mass and restitution. Then, you will be able to detect collisions and add gameplay features accordingly.

Prerequisites

Before starting this tutorial we recommend checking the [rendering] and [physics] tutorials.

Final result

You can download the resulting project in each interface here:

Let's start

As discussed in [key concepts], all the data of the game is stored in entities. This includes visual entities such as the ball but also non visual elements like the game rules.

First, we will identify all the entities in Pong. We have the ball, the two paddles that the players can control, the top and bottom wall, the two goals, the score counters and an extra entity for holding the game rules.

Some of these entities have a lot of data in common. For example, the top and bottom wall only differ in their position, but their scale and physical properties are the same. Instead of creating all entities from scratch, we will define a prefab and create two variants of that prefab as our final walls.

What data do we need to define a wall? This depends on the abstraction we make. In this example, we have decided that we will need a position, scale and a sprite. Since the position changes in the instances we will only add the common elements:

We will start by defining the visual properties of the entities. Then, continue with the physical ones.

    "wall"
    {
        scale 100 2, sprite "wall"
    }

Now our walls will be visible. In order to interact with the walls, we will need to add physical properties to them. We will add a box collider to detect collisions with other physical bodies and avoid penetrations and a restitution value of 1 to make the collision bouncy (perfectly elastic).

    wall has
    {
        scale 100 2, sprite wall,
        extent 1 1, restitution 1
    }

Notice that the size of the box collider is relative to the scale of the entity.

Let us continue with the ball.

    ball has
    {
        scale 2 2, sprite ball,
        radius 0.5, restitution 1
    }

It is very similar to a wall, except it uses a circle collider with a radius that fits its scale. However, the ball takes part in more interactions, and as such we must add more data to it.

Tag components are very useful to mark entities that take part in certain interactions. We can distinguish if an entity is to be processed by a system if it contains a tag or not. In pong, we want the ball to be part of the serving, scoring and respawning interactions. For simplicity, we will add a tag for each of those interactions.

    ball has
    {
        scale 2 2, sprite ball,
        radius 0.5, restitution 1,
        serveTag, scoreTag, respawnTag,
        respawnPosition 0 0
    }

Ending the components with the word Tag is not necessary, it is just a naming convention. The particular thing about tags is that they don't carry any data, making them specially efficient to filter.

For the paddle, we need to include some sort of input controls to control its movement. We will create a component that holds the name of the input that will control the paddle, in this case, the left stick Y axis of the gamepad. Also, the direction component will control in which direction moves this paddle, its modulus being the speed.

    paddle has
    {
        scale 2 5, sprite paddle,
        extent 1 1, restitution 1,
        motion gamepad left Y, direction 0 10
    }

Make sure that the code compiles and you can open the project in the game engine. You should see the entity prefabs under the Design/Prefabs/ folder. Next we will add our first system for interaction:

    control
    {
        for all entity paddle
        {
            paddle.velocity = paddle.direction * paddle.motionValue
        }
    }
    respawn
    {
        for all entity collision with enterCollision
        {
            if has(collision.first, respawnTag)
            {
                collision.first.position = collision.first.respawnPosition
            }
        }
    }
    serve
    {
        for all entity ball with serveTag
        {
            angle = random(ball.angleRange)

            ball.velocity = join(cos(angle),sin(angle)) * random(ball.lengthRange)
        }
    }
    score
    {
        for all entity collision with enterCollision
        {
            if has(collision.first, scoreTag)
            {
                for all entity board
                {
                    if board.team = collision.second.team
                    {
                        board.score += collision.second.worth
                    }
                }
            }
        }
    }
    restart
    {
        for all entity rules
        {
            for all entity board
            {
                if board.score >= rules.maximumScore
                {
                    count++
                }
            }

            if count >= rules.minimumBoardsOverScore
            {
                for all entity any
                {
                    destroy(any)
                }

                create(rules.scene)
            }
        }
    }

What's next

Offer little exercises or the next tutorial.

Have questions

Post them somewhere, open an issue?